package net.kucoe.grin.internal;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import net.kucoe.grin.Grin;

/**
 * Utility for constructor analyzing and object creation.
 * 
 * @author Vitaliy Basyuk
 */
public class ConstructorSteppingCreator {
    
    private static final Logger logger = Grin.getLogger("ObjectCreator");
    
    /**
     * Create object looking through all constructors.
     * 
     * @param clazz class
     * @param objects arguments
     * @return object
     */
    @SuppressWarnings("unchecked")
    public static <T> T create(final Class<T> clazz, final Object... objects) {
        if (clazz == null) {
            return null;
        }
        Object[] args = objects;
        if (args == null) {
            args = new Object[0];
        }
        Constructor<?>[] constructors = clazz.getConstructors();
        logger.config("Creating object of class " + clazz);
        return (T) createUntilReady(constructors, args);
    }
    
    private static Object createUntilReady(final Constructor<?>[] constructors, final Object... objects) {
        List<Constructor<?>> skip = new LimitedList<Constructor<?>>(constructors.length);
        Constructor<?> constructor = null;
        Object result = null;
        Stack<Object> stack = new Stack<Object>();
        for (Object object : objects) {
            stack.push(object);
        }
        while (result == null) {
            constructor = getBestPassConstructor(constructors, skip, objects);
            if (constructor == null) {
                break;
            }
            result = createAndCatch(constructor, stack.toArray());
            skip.add(constructor);
            if (skip.size() == constructors.length || stack.isEmpty()) {
                break;
            }
            stack.pop();
        }
        return result;
    }
    
    private static Object createAndCatch(final Constructor<?> constructor, final Object... objects) {
        try {
            return constructor.newInstance(objects);
        } catch (IllegalArgumentException e) {
            return null;
        } catch (InstantiationException e) {
            return null;
        } catch (IllegalAccessException e) {
            return null;
        } catch (InvocationTargetException e) {
            return null;
        }
    }
    
    private static Constructor<?> getBestPassConstructor(final Constructor<?>[] constructors,
        final List<Constructor<?>> skip, final Object... objects) {
        List<Constructor<?>> numberPassed = getArgsNumberPassed(constructors, objects.length);
        for (Constructor<?> constructor : numberPassed) {
            if (skip.contains(constructor)) {
                continue;
            }
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            boolean passing = true;
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> type = parameterTypes[i];
                Object object = objects[i];
                if (!type.isInstance(object)) {
                    passing = false;
                    continue;
                }
            }
            if (passing) {
                return constructor;
            }
        }
        return null;
    }
    
    private static List<Constructor<?>> getArgsNumberPassed(final Constructor<?>[] constructors, final int length) {
        List<Constructor<?>> list = new LimitedList<Constructor<?>>(constructors.length);
        for (Constructor<?> constructor : constructors) {
            if (constructor.getParameterTypes().length == length) {
                list.add(constructor);
            }
        }
        return list;
    }
    
}
